package com.kay.framework.servlet;

import com.kay.framework.annotation.*;
import com.kay.framework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @Description:
 * @Author: Kay
 * @CreateDate: 2021/7/12$ 14:19$
 * @UpdateUser: Kay
 * @UpdateDate: 2021/7/12$ 14:19$
 */
public class DispatcherServlet extends HttpServlet {
    private Properties properties = new Properties(); // 配置文件读取的信息封装到此Properties对象中

    private List<String> classPathNameList = new ArrayList<>(); // 所有扫描包下类的全限定类名

    private Map<String, Object> beanMap = new HashMap<>(); // ioc bean的实例

    private List<Handler> handlerList = new ArrayList<>(); // url 与 handler 映射关系

    private Map<String, Set<String>> securityMap = new HashMap<>(); // 存放了 url 与 对应的权限用户列表 之间的映射关系

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 根据request获取到请求URI，根据URL获取到对应的Handler对象；
     * 根据Handler信息，获取到参数信息结合request中的parameter封装请求参数，调用method.invoke调用对应的方法
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write(" 404 handler can not find ~~~");
            return;
        }
        // ------------ 判断权限 --------
        if (!hasSecurityAuth(req)) {
            resp.getWriter().write(" 401 forbidden ~~~ !!! ");
            return;
        }
        Method method = handler.getMethod();
        Map<String, Integer> paramsIndexMapping = handler.getParamsIndexMapping();
        Object[] args = new Object[paramsIndexMapping.size()]; // 要传入的参数数组
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> stringEntry : parameterMap.entrySet()) {
            String parameterName = stringEntry.getKey();
            if (paramsIndexMapping.containsKey(parameterName)) {
                String parameterValue = StringUtils.join(stringEntry.getValue(), ","); // 传入的值
                args[paramsIndexMapping.get(parameterName)] = parameterValue; // 将传入的参数值封装到 传参数组中的指定位置
            }
        }
        // 单独处理HttpServletRequest, HttpServletResponse
        if (paramsIndexMapping.containsKey(HttpServletRequest.class.getSimpleName())) {
            args[paramsIndexMapping.get(HttpServletRequest.class.getSimpleName())] = req;
        }

        if (paramsIndexMapping.containsKey(HttpServletResponse.class.getSimpleName())) {
            args[paramsIndexMapping.get(HttpServletResponse.class.getSimpleName())] = resp;
        }

        try {
            method.invoke(handler.getController(), args); //反射调用controller中的指定方法
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    /**
     * 判断用户是否有对应URL的访问权限
     * @param req
     * @return
     */
    private boolean hasSecurityAuth(HttpServletRequest req) {
        String requestURI = req.getRequestURI();
        if (securityMap.containsKey(requestURI)){
            Set<String> usernameSet = securityMap.get(requestURI);
            if (usernameSet.size() <= 0) return false; // 对所有用户都不开放

            Object username = req.getParameter("username");
            if (username == null) return false; // 相当于用户没有登陆 todo，可以优化成没有用户登陆时，返回登陆信息也就是携带username

            if (!usernameSet.contains(username.toString())) return false; // 相当于已登陆的用户没有对应权限
        }
        return true;
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerList.size() == 0) return null;

        String requestURI = req.getRequestURI();
        for (Handler handler : handlerList) {
            if (handler.getPattern().matcher(requestURI).matches()) {
                return handler;
            }
        }
        return null;
    }

    /**
     * 读取初始化参数，拿到待解析的文件；
     * 解析文件，获取到Properties对象，包含待扫描的包名；
     * 扫描包下所有类信息，获取到所有的类的全限定类名信息；
     * 通过反射，根据类中使用到的@Controller/@Service注解实例化对象；
     * 通过反射，根据类中使有到的@Autowired注解为对象注入属性；
     * 通过反射，获取@Controller类下的@RequestMapping注解，封装Handler信息，绑定对象、方法对象、方法对应的参数信息、请求的URL信息（Pattern）等
     *
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1. 读取初始化参数，拿到待解析的文件；
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");

        // 2.解析文件，获取到文件基本信息，并且拿到待扫描包名
        doParseConfig(contextConfigLocation);

        // 3. 扫描包下所有类信息，获取到所有的类的全限定类名信息；
        doScanPackage(properties.getProperty("componentScan"));

        // 4. 通过反射，根据类中使用到的@Controller/@Service注解实例化对象；
        doInstanceObject();

        // 5.通过反射，根据类中使有到的@Autowired注解为对象注入属性;
        doInject();

        // 6. 通过反射，获取@Controller类下的@RequestMapping注解，封装Handler信息，绑定对象、方法对象、方法对应的参数信息、请求的URL信息（Pattern）等
        doInitHandlerMapping();

        // 7. 通过反射，获取 所有使用了 @Security 的Controller或者handler，将URL与权限进行绑定
        doBindSecurity();

        System.out.println(" my spring mvc 初始化完成 ~~~ ");
    }

    /**
     * 绑定URL与权限
     */
    private void doBindSecurity() {
        if (beanMap.size() == 0) return;

        for (Map.Entry<String, Object> objectEntry : beanMap.entrySet()) {
            Object object = objectEntry.getValue();
            Class<?> aClass = object.getClass();
            if (!aClass.isAnnotationPresent(Controller.class)) continue; // 必须是Controller层的配置

            // 类上有Security注解 将类上的权限作为初始结果集存放起来 每个handler的权限集为 handler 和 Controller 的交集

            String URI = "";
            if (aClass.isAnnotationPresent(RequestMapping.class))
                URI = aClass.getAnnotation(RequestMapping.class).value();

            Method[] methods = aClass.getMethods();

            if (aClass.isAnnotationPresent(Security.class)) { // 类上有Security 注解
                Set<String> initValue = new HashSet<>(Arrays.asList(aClass.getAnnotation(Security.class).value()));

                for (Method method : methods) {
                    if (!method.isAnnotationPresent(RequestMapping.class)) continue;

                    String url = URI + method.getAnnotation(RequestMapping.class).value();

                    if (method.isAnnotationPresent(Security.class)) { // 方法上有注解
                        Security handlerSecurity = method.getAnnotation(Security.class);
                        List<String> handlerValue = Arrays.asList(handlerSecurity.value());
                        // 类上和方法上同时 有 Security 注解  Security上设置的是可以通过的username ,所以： 可以通过的用户为两个集合的交集
                        Set<String> result = new HashSet(initValue);
                        result.retainAll(handlerValue); // 两个集合的交集
                        securityMap.put(url, result);
                    } else { // 方法上无Security注解
                        securityMap.put(url, initValue);
                    }
                }
            } else { // 类上无Security注解
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(RequestMapping.class)) continue;

                    String url = URI + method.getAnnotation(RequestMapping.class).value();

                    if (method.isAnnotationPresent(Security.class)) { // 方法上有注解
                        securityMap.put(url, new HashSet(Arrays.asList(method.getAnnotation(Security.class).value())));
                    }
                }
            }
        }
    }

    /**
     * 通过反射，获取@Controller类下的@RequestMapping注解
     * 封装Handler信息，绑定对象、方法对象、方法对应的参数信息、请求的URL信息（Pattern）等
     */
    private void doInitHandlerMapping() {
        if (beanMap.size() == 0) return;

        for (Map.Entry<String, Object> objectEntry : beanMap.entrySet()) {
            Object object = objectEntry.getValue();
            Class<?> aClass = object.getClass();

            if (!aClass.isAnnotationPresent(Controller.class)) continue;
            // 请求URI：类的RequestMapping + 方法上的RequestMapping
            String URI = "";
            if (aClass.isAnnotationPresent(RequestMapping.class))
                URI = aClass.getAnnotation(RequestMapping.class).value();

            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(RequestMapping.class)) continue;

                RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);

                String url = URI + methodAnnotation.value();

                // Object controller, Method method, Pattern pattern
                Handler handler = new Handler(object, method, Pattern.compile(url));

                // 处理方法对应的参数信息   paramsIndexMapping; // key： 参数名  value : 参数的顺序
                Map<String, Integer> paramsIndexMapping = handler.getParamsIndexMapping();
                Parameter[] parameters = method.getParameters(); // 方法的所有参数
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    // HttpServletRequest, HttpServletResponse 单独处理
                    Class<?> parameterType = parameter.getType();
                    if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
                        paramsIndexMapping.put(parameterType.getSimpleName(), j);
                    } else {
                        paramsIndexMapping.put(parameter.getName(), j); // 名称 ： 参数顺序
                    }
                }
                handlerList.add(handler);
            }
        }
    }

    /**
     * 通过反射，根据类中使有到的@Autowired注解为对象注入属性;
     */
    private void doInject() {
        if (beanMap.size() == 0) return;

        for (Map.Entry<String, Object> objectEntry : beanMap.entrySet()) {
            Object object = objectEntry.getValue();
            Class<?> aClass = object.getClass();
            Field[] declaredFields = aClass.getDeclaredFields(); // 获取所有的属性
            for (Field declaredField : declaredFields) {
                // 无@Autowired注解的属性，不处理
                if (!declaredField.isAnnotationPresent(Autowired.class)) continue;

                Autowired autowired = declaredField.getAnnotation(Autowired.class);
                // beanId: 如果@Autowired配置了value 以value为准 ;未配置，则默认为注入的属性类型全限定类名
                String beanId = autowired.value();
                if (StringUtils.isNotBlank(beanId)) beanId = declaredField.getType().getName();

                declaredField.setAccessible(true); //
                try {
                    declaredField.set(object, beanMap.get(beanId));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 通过反射，根据类中使用到的@Controller/@Service注解实例化对象；
     */
    private void doInstanceObject() {
        if (classPathNameList.size() == 0) return;

        try {
            for (String classPathName : classPathNameList) {
                Class<?> aClass = Class.forName(classPathName);

                if (aClass.isAnnotationPresent(Controller.class)) {
                    // 针对controller 不会被其他类调用，直接beanId默认为类名首字母小写
                    beanMap.put(lowerFirst(aClass.getSimpleName()), aClass.newInstance());
                } else if (aClass.isAnnotationPresent(Service.class)) {
                    Service serviceAnnotation = aClass.getAnnotation(Service.class);
                    // service注解设置了value，benId以定义为准，没有设置，默认为接口类名首字母小写
                    String beanId = serviceAnnotation.value();
                    if (StringUtils.isNotBlank(beanId)) {
                        beanId = lowerFirst(aClass.getSimpleName());
                    }
                    beanMap.put(beanId, aClass.newInstance());

                    // 维护类对应的接口 用于属性注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        beanMap.put(anInterface.getName(), aClass.newInstance()); // todo 此处存放的对象是新的对象实例
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private String lowerFirst(String target) {
        if (StringUtils.isBlank(target)) return target;

        char[] chars = target.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 扫描包下所有类信息，获取到所有的类的全限定类名信息；
     *
     * @param componentScan
     */
    private void doScanPackage(String componentScan) { // com.kay
        // 1. 获取文件的磁盘地址
        String filePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + componentScan.replaceAll("\\.", "/");

        // 2. 递归读取文件夹，获取.class文件路径保存到 classPathNameList中
        File file = new File(filePath);
        File[] files = file.listFiles();

        for (File file1 : files) {
            String file1Name = file1.getName();
            if (file1.isDirectory()) {
                doScanPackage(componentScan + "." + file1Name); // com.kay.controller
            }

            if (file1Name.endsWith(".class")) {
                classPathNameList.add(componentScan + "." + file1Name.replaceAll(".class", ""));
            }
        }
    }

    /**
     * 根据传入的文件地址，解析文件，获取到Properties对象，包含待扫描的包名；
     *
     * @param contextConfigLocation
     */
    private void doParseConfig(String contextConfigLocation) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
